// TODO: CODE REVIEW & CLEANUP!
//
// IMusicWcfService.cs
//
// Defines the WCF interface to the HomeUX Music Server.
//
// Note: #define SIMULATED_MUSIC_SERVER if you want the interface to include asynchronous methods
// as well as the synchronous ones.  This is useful within a Silverlight compilation context;
// outside of Silverlight, IMusicWcfServiceAsync.cs can be used.  (The latter won't work in
// Silverlight because that generated file contains references that won't resolve in a
// Silverlight application.)
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace HomeUX.MusicServer
{

// NOTE: If you change the interface name "IMusicWcfService" here, you must also update the reference to "IMusicWcfService" in App.config.
[ServiceContract(
    CallbackContract = typeof(IMusicWcfServiceCallback),
#if !SILVERLIGHT
    SessionMode = SessionMode.Required,
#endif
    Namespace = "http://schemas.HomeUX.net/HomeUX/1.0/server")]
public interface IMusicWcfService
{
    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void Hello();
#if SIMULATED_MUSIC_SERVER
	IAsyncResult BeginHello(AsyncCallback callback, object asyncState);
	void EndHello(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void Goodbye();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGoodbye(AsyncCallback callback, object asyncState);
	void EndGoodbye(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    ArtistInfo[] GetArtists();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGetArtists(AsyncCallback callback, object asyncState);
	ArtistInfo[] EndGetArtists(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    AlbumInfo[] GetAlbumsOfArtist(string artistName, GetAlbumsOfArtistOptions options);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGetAlbumsOfArtist(string artistName, GetAlbumsOfArtistOptions options, AsyncCallback callback, object asyncState);
	AlbumInfo[] EndGetAlbumsOfArtist(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    AlbumDetails GetAlbumDetails(string albumId);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGetAlbumDetails(string albumId, AsyncCallback callback, object asyncState);
	AlbumDetails EndGetAlbumDetails(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    SearchResult Search(SearchRequest searchRequest);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginSearch(SearchRequest searchRequest, AsyncCallback callback, object asyncState);
	SearchResult EndSearch(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    QueueDetails GetQueueDetails();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGetQueueDetails(AsyncCallback callback, object asyncState);
	QueueDetails EndGetQueueDetails(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void PlayAlbum(string albumId, int startAtTrackNumber);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginPlayAlbum(string albumId, int startAtTrackNumber, AsyncCallback callback, object asyncState);
	void EndPlayAlbum(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void AddToQueue(TrackId[] trackIds);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginAddToQueue(TrackId[] trackIds, AsyncCallback callback, object asyncState);
	void EndAddToQueue(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void RemoveFromQueue(TrackId[] trackIds);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginRemoveFromQueue(TrackId[] trackIds, AsyncCallback callback, object asyncState);
	void EndRemoveFromQueue(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void ChangeQueue(TrackId[] trackIds);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginChangeQueue(TrackId[] trackIds, AsyncCallback callback, object asyncState);
	void EndChangeQueue(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void ClearQueue();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginClearQueue(AsyncCallback callback, object asyncState);
	void EndClearQueue(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    PlaylistInfo[] GetPlaylists();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGetPlaylists(AsyncCallback callback, object asyncState);
	PlaylistInfo[] EndGetPlaylists(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void LoadPlaylist(string playlistName);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginLoadPlaylist(string playlistName, AsyncCallback callback, object asyncState);
	void EndLoadPlaylist(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void SaveQueue(string playlistName);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginSaveQueue(string playlistName, AsyncCallback callback, object asyncState);
	void EndSaveQueue(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void DeletePlaylist(string playlistName);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginDeletePlaylist(string playlistName, AsyncCallback callback, object asyncState);
	void EndDeletePlaylist(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void Play();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginPlay(AsyncCallback callback, object asyncState);
	void EndPlay(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void Pause();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginPause(AsyncCallback callback, object asyncState);
	void EndPause(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void Stop();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginStop(AsyncCallback callback, object asyncState);
	void EndStop(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void FastForward();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginFastForward(AsyncCallback callback, object asyncState);
	void EndFastForward(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void FastReverse();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginFastReverse(AsyncCallback callback, object asyncState);
	void EndFastReverse(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void NextTrack();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginNextTrack(AsyncCallback callback, object asyncState);
	void EndNextTrack(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void PreviousTrack();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginPreviousTrack(AsyncCallback callback, object asyncState);
	void EndPreviousTrack(IAsyncResult asyncResult);
#endif

    // plays the item if it's in the queue; does nothing if it isn't
    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void PlayQueueItem(TrackId trackId);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginPlayQueueItem(TrackId trackId, AsyncCallback callback, object asyncState);
	void EndPlayQueueItem(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    MusicServiceDetailedStatus GetDetailedStatus();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGetDetailedStatus(AsyncCallback callback, object asyncState);
	MusicServiceDetailedStatus EndGetDetailedStatus(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    MusicServiceSettings GetSettings();
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginGetSettings(AsyncCallback callback, object asyncState);
	MusicServiceSettings EndGetSettings(IAsyncResult asyncResult);
#endif

    [OperationContract]
    [FaultContract(typeof(MusicServiceFault))]
    void SetSettings(MusicServiceSettings settings);
#if SIMULATED_MUSIC_SERVER
    IAsyncResult BeginSetSettings(MusicServiceSettings settings, AsyncCallback callback, object asyncState);
	void EndSetSettings(IAsyncResult asyncResult);
#endif
}

[ServiceContract(Namespace = "http://schemas.HomeUX.net/HomeUX/1.0/server")]
public interface IMusicWcfServiceCallback
{
    // note that "OperationContract(IsOneWay = true)" also means "invoke on client on worker
	// thread", which avoids deadlocks

    // if there's no current track, albumId is null and trackNumber and position are 0
    [OperationContract(IsOneWay = true)]
    void StatusChanged(MusicServiceStatus status);

    [OperationContract(IsOneWay = true)]
    void QueueChanged(QueueDetails queueDetails);

    [OperationContract(IsOneWay = true)]
    void LibraryChanged();

    [OperationContract(IsOneWay = true)]
    void SettingsChanged(MusicServiceSettings settings);
}

/// <summary>
/// Note that in IMusicWcfService.GetSettings and IMusicWcfServiceCallback.SettingsChanged,
/// all nullable fields will be non-null, but for IMusicWcfService.SetSettings any nullable
/// fields may be null if their values don't need to change.
/// </summary>
public class MusicServiceSettings
{
    public int? Volume; // 0 to 100 -- this is Media Player local volume, not system volume
    public bool? IsMuted;
    public double? PlaybackRate; // see IWMPSettings::rate; 1.0 means "normal rate"
    public bool? IsLoopMode;
    public bool? IsShuffleMode;
}

[DebuggerDisplay("{PlayState}")]
public class MusicServiceStatus
{
    public MusicServicePlayState PlayState;
    public string AlbumId;
    public int TrackNumber;
    public double Position;
    public int QueueIndex;
}

public class MusicServiceDetailedStatus : MusicServiceStatus
{
    public string ArtistName;
    public string AlbumName;
    public byte[] AlbumArt;

    public string TrackName;
    public double TrackDuration;
    public int TrackYear;
    public string TrackComposer;

    public int QueuedTrackCount;
    public double QueuedTrackDuration;

    public MusicServiceSettings Settings;
}

[DebuggerDisplay("{Name}")]
public class ArtistInfo
{
    public string Name;
    public string SortKey;
}

[DebuggerDisplay("{Id}")]
public class AlbumInfo
{
    public string Id;
    public string Name;
    public byte[] AlbumArt;
}

[DebuggerDisplay("{Name}")]
public class AlbumDetails
{
    public string ArtistName;
    public string Name;
    public byte[] AlbumArt;
    public TrackDetails[] Tracks; // element i is for track i+1
}

[DebuggerDisplay("{AlbumId} {TrackNumber}")]
public class TrackId
{
    public string AlbumId;
    public int TrackNumber;
}

[DebuggerDisplay("{Name}")]
public class TrackDetails
{
    public int TrackNumber;
    public string Name;
    public double Duration;
    public int Year;
    public string Composer;
}

[DebuggerDisplay("{TrackDetails.Name}")]
public class AlbumTrackDetails
{
    public string ArtistName;
    public string AlbumId;
    public string AlbumName;
    public TrackDetails TrackDetails;
}

public class QueueDetails
{
    public AlbumTrackDetails[] Tracks;
}

[DebuggerDisplay("Find {Find}")]
public class SearchRequest
{
    public string Find;

    /// <summary>
    /// The maximum number of artists to return in <r>SearchResult.FoundArtists</r>.  If this
    /// number is negative, then no search will be performed for artists at all, and
    /// <r>SearchResult.FoundArtistCount</r> will be -1.
    /// </summary>
    public int MaxArtists;

    /// <summary>
    /// The maximum number of albums to return in <r>SearchResult.FoundAlbums</r>.  If this
    /// number is negative, then no search will be performed for albums at all, and
    /// <r>SearchResult.FoundAlbumCount</r> will be -1.
    /// </summary>
    public int MaxAlbums;

    /// <summary>
    /// The maximum number of tracks to return in <r>SearchResult.FoundTracks</r>.  If this
    /// number is negative, then no search will be performed for tracks at all, and
    /// <r>SearchResult.FoundTrackCount</r> will be -1.
    /// </summary>
    public int MaxTracks;

    /// <summary>
    /// If <n>true</n>, <r>FoundAlbum.AlbumArt</r> will be filled in when album art is available.
    /// </summary>
    public bool GetAlbumArt;
}

[DebuggerDisplay("{FoundArtistCount} artists, {FoundAlbumCount} albums, {FoundTrackCount} tracks")]
public class SearchResult
{
    public int FoundArtistCount;
    public int FoundAlbumCount;
    public int FoundTrackCount;

    public FoundArtist[] FoundArtists;
    public FoundAlbum[] FoundAlbums;
    public FoundTrack[] FoundTracks;
}

[DebuggerDisplay("{ArtistName}")]
public class FoundArtist
{
    public string ArtistName;
}

[DebuggerDisplay("{AlbumId}")]
public class FoundAlbum
{
    public string AlbumId;
    public string ArtistName;
    public string AlbumName;
    public byte[] AlbumArt;
}

[DebuggerDisplay("{TrackName}")]
public class FoundTrack
{
    public TrackId TrackId;
    public string ArtistName;
    public string AlbumName;
    public string TrackName;
}

[DebuggerDisplay("{Name}")]
public class PlaylistInfo
{
    public string Name;
}

public enum MusicServicePlayState
{
    /// <summary>
    /// There's no "current track" -- for example, the queue is empty.
    /// </summary>
    NoCurrentTrack,

    /// <summary>
    /// Playback is stopped.  Playback will resume at the beginning of the current track.
    /// </summary>
    Stopped,

    /// <summary>
    /// Playback is paused.  Playback will resume at the current location within the current track.
    /// </summary>
    Paused,

    /// <summary>
    /// The current track is playing.
    /// </summary>
    Playing,

    /// <summary>
    /// "Fast forward" is occuring.
    /// </summary>
    ScanForward,

    /// <summary>
    /// "Rewind" is occuring.
    /// </summary>
    ScanReverse
}

/*
public enum FindWhat
{
    /// <summary>
    /// Return counts of matching artists, albums, and tracks.
    /// </summary>
    Counts,

    /// <summary>
    /// Returns detailed list of matching artists.
    /// </summary>
    Artists,

    /// <summary>
    /// Returns detailed list of matching albums.
    /// </summary>
    Albums,

    /// <summary>
    /// Returns detailed list of matching tracks.
    /// </summary>
    Tracks
}
*/

[Flags]
public enum GetAlbumsOfArtistOptions
{
    GetAlbumArt = 1
}

[DataContract]
public class MusicServiceFault
{
    private string _message;

    [DataMember]
    public string Message
    {
        get
        {
            return _message;
        }
        set
        {
            _message = value;
        }
    }
}

static class MusicServerHelper
{
    public static bool AreTracksEqual(TrackId trackId1, TrackId trackId2)
    {
        // check if either <trackId1> or <trackId2> are null
        if (trackId1 == null)
        {
            if (trackId2 == null)
                return true;
        }
        else
        {
            if (trackId2 == null)
                return false;
        }

        return (trackId1.AlbumId == trackId2.AlbumId) &&
               (trackId1.TrackNumber == trackId2.TrackNumber);
    }

    static IEqualityComparer<TrackId> _equalityComparer;

    public static IEqualityComparer<TrackId> TrackIdEqualityComparer
    {
        get
        {
            if (_equalityComparer == null)
                _equalityComparer = new TrackIdEqualityComparerImplementation();
            return _equalityComparer;
        }
    }

    class TrackIdEqualityComparerImplementation : IEqualityComparer<TrackId>
    {
        public bool Equals(TrackId x, TrackId y)
        {
            return AreTracksEqual(x, y);
        }

        public int GetHashCode(TrackId trackId)
        {
            return trackId.AlbumId.GetHashCode() ^ trackId.TrackNumber.GetHashCode();
        }
    }
}

}
